#pragma once

//TODO:
//  - Sutherland-Hodgeman clipping against Z = 0
//  - Move rendering core to renderer<software_tag> instead of the vertex_buffer
//  - (Or move it to a core::job sub-class)

#include "fixed_point_helper.h"
#include "vertex_buffer.h"
#include "triangle_renderer_software.h"
#include "renderer.h"

#include <iostream>


//// TODO: come up with a scheme to give render_job the correct canvas and zbuffer
////    (can't use thread local storage)

//class render_job : public core::job{
//public:
//	render_job(mesh &a_Mesh, canvas *a_Canvas, z_buffer *a_ZBuffer) {}
//
//	virtual void execute(){
//		/// render it like we're used to
//	}
//};
//
//class render_merge_job : public core::job{
//public:
//	render_merge_job(canvas *result, canvas *a, canvas *b, z_buffer *az, z_buffer *bz) {}
//
//	virtual void execute(){
//		// foreach pixel in zbuffer
//		//   if(depth(az) <  depth(bz))
//		//     put_pixel_of(a onto result);
//		//   else
//		//     put_pixel_of(b onto result);
//	}
//
//	virtual void done(){
//		// plot image to screen?
//	}
//};

namespace krryn{
namespace scene{
	template
	<
		typename _Vertex
	>
	class vertex_buffer<system::software_tag, _Vertex> : public hardware_buffer{
	public:
		static const bool m_IsVertexBuffer = true;

		typedef _Vertex vertex_type;
		typedef typename _Vertex::description_type description_type;
		typedef krryn::core::refc_ptr<vertex_buffer> ptr;
		typedef index_buffer<system::software_tag> index_buffer_type;

		//typedef system_imp::fp_type type;
		typedef float type;
		//typedef double type;

		vertex_buffer(system::renderer<system::software_tag> &a_Renderer, size_t a_Size) 
			: m_Size(a_Size), m_Renderer(a_Renderer){
			// allocate a fixed size vector for performance.
			// this also guarantees us that the m_Current iterator will never
			// invalid as long as we don't have to resize the vector 
			// (which we don't because operator += does bounds checking).

			m_Vertices = new type[a_Size * description_type::m_Size];
			m_Current = m_Vertices;

			// set to actual values in setup_for_drawing()
			m_HalfWidth = -1;
			m_HalfHeight = -1; 

			/// REMOVE
			m_Texture = new system_imp::texture_software(
				new view::canvas("./q4/teldrop_s.tga"), 0);

			std::clog << "Using: " << typeid(type).name() << " pipeline" << std::endl;
		}

		~vertex_buffer(){
			delete[] m_Vertices;
		}

		void lock(lock_type a_Type){
			hardware_buffer::lock(a_Type);
		}

		void unlock(){
			hardware_buffer::unlock();
		}

		void draw(){
			setup_for_drawing();
		}

		void draw_indexed(const index_buffer_type &a_Buffer){
			setup_for_drawing();

			m_TriangleRenderer.set_texture(m_Texture);

			// transform from clip-space to screen space in a blunt-axe type of way
			math::matrix m;
			static float f = 1;
			f += 0.1f;
			m.scale(0.01f);
			m.set_translation(math::vector3f(0, 0, 2));

			matrix r;
			r.rotate_z(f);
			m.concatenate(r);

			matrix l_Perspective = matrix::create_perspective_projection(1.f, 100.f, 1, .75);
			l_Perspective.concatenate(m);

			// convert matrix to fixed point
			type l_Matrix[16];
			for(int i = 0; i < 16; i++)
				l_Matrix[i] = cast_to<type>(l_Perspective.cell[i]);

			typedef typename index_buffer_type::const_iterator iter;

			for(iter i = a_Buffer.begin(); i != a_Buffer.end(); i += 3){
				memcpy(
					m_TempVertex[0], 
					m_Vertices + (i[0] * description_type::m_Size), 
					sizeof(type) * description_type::m_Size
				);

				memcpy(
					m_TempVertex[1],
					m_Vertices + (i[1] * description_type::m_Size),
					sizeof(type) * description_type::m_Size
				);

				memcpy(
					m_TempVertex[2], 
					m_Vertices + (i[2] * description_type::m_Size), 
					sizeof(type) * description_type::m_Size
				);

				transform(m_TempVertex[0], l_Matrix);
				transform(m_TempVertex[1], l_Matrix);
				transform(m_TempVertex[2], l_Matrix);

				/// back-face culling check in screen space
				int l_Backface[2] = {
					  to_int<type>(m_TempVertex[2][0] - m_TempVertex[0][0])
					* to_int<type>(m_TempVertex[1][1] - m_TempVertex[0][1]),
					  to_int<type>(m_TempVertex[2][1] - m_TempVertex[0][1])
					* to_int<type>(m_TempVertex[1][0] - m_TempVertex[0][0])
				};

				if(l_Backface[0] < l_Backface[1]){
					m_TriangleRenderer.draw(
						m_TempVertex[0], 
						m_TempVertex[1],
						m_TempVertex[2]
					);
				}
			}
		}

		void operator +=(const _Vertex &a_Vertex){
			if(locked() ^ write){
				throw scene_exception("Buffer must be write-locked for insertion");
			}

			type *l_End = m_Vertices + m_Size * description_type::m_Size;

			if(m_Current >= l_End){
				throw scene_exception("Buffer out of bounds");
			}

			float l_VertexData[description_type::m_Size];

			a_Vertex.copy_into(
				(char*)l_VertexData, // begin
				(char*)(l_VertexData + description_type::m_Size) // end
			);

			/// convert each item to fixed point individually
			for(size_t i = 0; i < description_type::m_Size; i++){
				*m_Current++ = cast_to<type>(l_VertexData[i]);
			}
		}

	private:
		template<typename _Type>
		void transform(_Type *a_Position, _Type *a_Matrix){
			// a_Position is assumed to be of length 3, w is assumed to be 1
			// a_Matrix is assumed to be of length 16, and row-major

			/// transform vertex to clip-space (matrix multiplication)
			_Type l_X = dot(a_Matrix +  0, a_Position);
			_Type l_Y = dot(a_Matrix +  4, a_Position);
			_Type l_Z = dot(a_Matrix +  8, a_Position);
			_Type l_W = dot(a_Matrix + 12, a_Position);

			/// perform Perspective Divide to get Normalized Device Coordinates
			l_X = div<_Type>(l_X, l_W);
			l_Y = div<_Type>(l_Y, l_W);
			l_Z = div<_Type>(l_Z, l_W);

			/// convert NDC to Screen Space

			/// x = w * (.5 * x + .5)
			a_Position[0] = mul<_Type>(m_HalfWidth, l_X) + m_HalfWidth;

			/// y = h * (.5 * y + .5)
			a_Position[1] = m_HalfHeight - mul<_Type>(m_HalfHeight, l_Y);

			/// z = .5 * z + .5
			a_Position[2] = 
				  mul<_Type>(fp_values<_Type>::half(), l_Z) 
				+ fp_values<_Type>::half();
		}

		template<typename _Type>
		forceinline _Type dot(_Type *a_Row, _Type *a_Colum){
			// a_Row is assumed to be 4
			// a_Column is assumed to be 3 (implicit w = 1)

			return 
				  mul<_Type>(a_Row[0], a_Colum[0])
				+ mul<_Type>(a_Row[1], a_Colum[1])
				+ mul<_Type>(a_Row[2], a_Colum[2])
				+ a_Row[3];
		}

		/// temporary vertex data, so we don't overwrite the data in this buffer
		type m_TempVertex[3][description_type::m_Size];

		/// current and starting position of this buffer
		type *m_Current;
		type *m_Vertices;

		type m_HalfWidth;
		type m_HalfHeight;

		size_t m_Size;

		system::renderer<system::software_tag> &m_Renderer;
		system_imp::triangle_renderer_software<_Vertex, type> m_TriangleRenderer;


		/// REMOVE
		system_imp::texture_software *m_Texture;

		void setup_for_drawing(){
			m_TriangleRenderer.set_canvas(m_Renderer.get_canvas());

			// the size of the canvas isn't know before this point, so we calculate it here
			m_HalfWidth  = cast_to<type>(.5f * (float)m_Renderer.get_canvas()->get_width());
			m_HalfHeight = cast_to<type>(.5f * (float)m_Renderer.get_canvas()->get_height());
		}
	};
}
}
